﻿// <copyright file="DataView.cs" company="Oleg V. Polikarpotchkin">
// Copyright © 2008-2009 Oleg V. Polikarpotchkin. All Right Reserved
// </copyright>
// <author>Oleg V. Polikarpotchkin</author>
// <email>ov-p@yandex.ru</email>
// <date>2009-05-26</date>
// <summary>OpenWPFChart library. DataView is the base class of all OpenWPFChart ViewModel classes.</summary>
// <revision>$Id$</revision>

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;

namespace OpenWPFChart.Parts
{
	/// <summary>
	/// <see cref="DataView"/> is the abstract base class of all OpenWPFChart ViewModel classes.
	/// </summary>
	/// <remarks>
	/// <para><see cref="DataView"/> defines the <see cref="DataView.VisualCue"/> property 
	/// which plays the key role in binding the <see cref="DataView"/> object to one or more
	/// visual objects of classes derived from the <see cref="ItemVisual"/> class.</para>
	/// </remarks>
	public abstract class DataView : ICloneable, INotifyPropertyChanged, IDataErrorInfo
	{
		#region RaiseMouseEnterLeaveItemEvents
		bool raiseMouseEnterLeaveItemEvents = true;
		/// <summary>
		/// Gets or sets the RaiseMouseEnterLeaveItemEvents property.
		/// This property controls whether MouseEnterItem/MouseLeaveItem Events raised.
		/// </summary>
		public bool RaiseMouseEnterLeaveItemEvents
		{
			get { return raiseMouseEnterLeaveItemEvents; }
			set
			{
				if (raiseMouseEnterLeaveItemEvents != value)
				{
					raiseMouseEnterLeaveItemEvents = value;
					NotifyPropertyChanged("RaiseMouseEnterLeaveItemEvents");
				}
			}
		}
		#endregion RaiseMouseEnterLeaveItemEvents

		#region VisualCue
		object visualCue;
		/// <summary>
		/// Gets or sets the VisualCue property.
		/// </summary>
		/// <remarks>
		/// This property is the key for binding the <see cref="DataView"/> object to one or more
		/// visual objects of classes derived from the <see cref="ItemVisual"/> class.
		/// <para>The <see cref="Item"/> type suppose that the value of this property is either some 
		/// <see cref="Type"/> object or is a collection of <see cref="Type"/> objects. Types are 
		/// supposed to be derived from the <see cref="ItemVisual"/> class.</para>
		/// <para>On initialization the <see cref="Item"/> object creates the visuals enumerated by
		/// this property value, binds them to its <see cref="Item.DataView"/> property, and
		/// adds them to its visual children collection.</para>
		/// <para>During its life cycle the <see cref="Item"/> object watches for changes in this
		/// property and when it happens updates its visuals accordingly.</para>
		/// </remarks>
		public virtual object VisualCue
		{
			get { return visualCue; }
			set
			{
				if (visualCue != value)
				{
					if (visualCue != null)
					{
						if (visualCue is INotifyCollectionChanged)
							(visualCue as INotifyCollectionChanged).CollectionChanged -= VisualCueCollectionChanged;
						else if (visualCue is IBindingList)
							(visualCue as IBindingList).ListChanged -= VisualCueListChanged;
					}

					visualCue = value;
					OnVisualCueChanged();

					if (visualCue != null)
					{
						if (visualCue is INotifyCollectionChanged)
							(visualCue as INotifyCollectionChanged).CollectionChanged += VisualCueCollectionChanged;
						else if (visualCue is IBindingList)
							(visualCue as IBindingList).ListChanged += VisualCueListChanged;
					}
				}
			}
		}

		/// <summary>
		/// VisualCue collection changed.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		/// <remarks>
		/// VisualCue is an observable collection.
		/// </remarks>
		void VisualCueCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			OnVisualCueChanged();
		}

		/// <summary>
		/// VisualCue list changed.
		/// </summary>
		/// <param name="sender">The sender.</param>
		/// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
		/// <remarks>
		/// VisualCue is a binding list.
		/// </remarks>
		void VisualCueListChanged(object sender, ListChangedEventArgs e)
		{
			OnVisualCueChanged();
		}

		/// <summary>
		/// Called when VisualCue property changed.
		/// </summary>
		protected virtual void OnVisualCueChanged() 
		{
			NotifyPropertyChanged("VisualCue");
		}
		#endregion VisualCue

		#region ICloneable Members
		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		/// <returns>
		/// A new object that is a copy of this instance.
		/// </returns>
		/// <remarks>
		/// <para>By default this method creates deep clones of Scales and VisualCue (if appropriate)
		/// but doesn't clone the <see cref="ItemData"/> property value.</para>
		/// <para>Override <see cref="CloneInternal"/> in derived classes to provide deep cloning
		/// facility.</para>
		/// </remarks>
		public object Clone()
		{
			return CloneInternal();
		}

		/// <summary>
		/// Creates a new object that is a copy of the current instance.
		/// </summary>
		/// <returns>
		/// A new object that is a copy of this instance.
		/// </returns>
		/// <remarks>This method is designed to provide the deep cloning facility if desired.</remarks>
		protected virtual object CloneInternal()
		{
			DataView clone = MemberwiseClone() as DataView;
			clone.PropertyChanged = null;
			
			// Clone VisualCue.
			if (clone.VisualCue is INotifyCollectionChanged)
				(clone.VisualCue as INotifyCollectionChanged).CollectionChanged -= VisualCueCollectionChanged;
			else if (clone.VisualCue is IBindingList)
				(clone.VisualCue as IBindingList).ListChanged -= VisualCueListChanged;
			if (clone.VisualCue is ICloneable)
				clone.VisualCue = (VisualCue as ICloneable).Clone();
			else if (clone.VisualCue is IEnumerable<Type>)
				clone.VisualCue = new ObservableCollection<Type>(VisualCue as IEnumerable<Type>);
			else if (clone.VisualCue is IEnumerable)
				clone.VisualCue = new ObservableCollection<object>((VisualCue as IEnumerable).Cast<object>());
			
			return clone;
		}
		#endregion ICloneable Members

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
		/// <summary>
		/// Notifies the property changed.
		/// </summary>
		/// <param name="propertyName">Name of the property.</param>
		protected void NotifyPropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
		}
		#endregion INotifyPropertyChanged Members

		#region IDataErrorInfo Members
		/// <summary>
		/// Gets an error message indicating what is wrong with this object.
		/// </summary>
		/// <value></value>
		/// <returns>
		/// An error message indicating what is wrong with this object. 
		/// The default is an <c>null</c>.
		/// </returns>
		string IDataErrorInfo.Error
		{
			get { return ObjectErrorInfo; }
		}

		/// <summary>
		/// Gets the <see cref="System.String"/> with the specified column name.
		/// </summary>
		/// <value>An error message indicating what is wrong with this column.</value>
		string IDataErrorInfo.this[string columnName]
		{
			get { return GetDataErrorInfo(columnName); }
		}

		/// <summary>
		/// Gets an error message indicating what is wrong with this object.
		/// </summary>
		/// <value></value>
		/// <returns>
		/// An error message indicating what is wrong with this object. 
		/// </returns>
		protected virtual string ObjectErrorInfo
		{
			get 
			{
				return null;
			}
		}

		/// <summary>
		/// Gets the data error info.
		/// </summary>
		/// <param name="columnName">Name of the column.</param>
		/// <returns>An error message indicating what is wrong with this column.</returns>
		protected virtual string GetDataErrorInfo(string columnName)
		{
			return null;
		}
		#endregion IDataErrorInfo Members
	}
}
